import { Matrix, Quaternion, Vector3 } from "@babylonjs/core/Maths/math.vector";
import { TransformNode } from "@babylonjs/core/Meshes/transformNode";
import { Scene } from "@babylonjs/core/scene";
import { Camera } from "@babylonjs/core/Cameras/camera";
import { AbstractControl } from "../control/abstract-control";
import { AbstractGroup } from "../control/abstract-group";
import { WebXRCamera } from "@babylonjs/core/XR/webXRCamera";
import { Utils } from "../../tool/utils";
import { BabylonContainer } from "../../base/babylon-container";
import { Axis } from "@babylonjs/core/Maths/math.axis";
import { AbstractComposite } from "../composite/abstract-composite";
import { Helper } from "../../tool/helper";


/**
 * ui节点参数对象
 * @param key 键名
 * @param anchor 锚点
 * @param position 位置
 * @param rotation 旋转
 * @param size 整体大小
 * @param billboard 朝向模式
 * @param lockToCamera 是否锁定到相机
 * @param depthForward 是否使用固定z向深度（而不是半径深度）
 * @param forward 向前的偏移值（锁定到相机时）
 * @param offset 位置偏移值
 * @param renderingGroupId 渲染顺序值
 * @param display 是否显示
 * @param xrRatio 沉浸模式中的比例
 */
interface IUINodeOptions {
    key?: string,
    anchor?: Vector3,
    position?: Vector3,
    rotation?: Quaternion,
    size?: number,
    billboard?: number,
    lockToCamera?: boolean,
    depthForward?: boolean,
    forward?: number,
    offset?: Vector3,
    renderingGroupId?: number,
    display?: boolean,
    xrRatio?: { x: number, y: number },
}


/**
 * ui节点
 * @param name 名称
 * @param scene 所在场景
 * @param options 参数对象
 */
class UINode {

    private _scene: Scene;
    private _camera: Camera | null;
    private _lockToCamera!: boolean;
    private _depthForward!: boolean;
    private _renderGroupId!: number;
    private _tv0 = new Vector3();
    private _tv1 = new Vector3();
    private _cameraSwitchHandle: { (): void };
    private _position = new Vector3();
    private _tmpBillboard: number = TransformNode.BILLBOARDMODE_NONE;
    private _anchor = new Vector3();
    private _scnToWld = new Vector3();
    private _controls: Array<AbstractControl | AbstractGroup | AbstractComposite> = [];
    private _size: number;
    private _xrRatio: { x: number, y: number };

    public readonly name: string;
    public readonly key: string;
    public readonly id = Utils.id;

    /**
     * ui节点的容器对象
     */
    public container: TransformNode;

    /**
     * ui节点的根对象
     */
    public root: TransformNode;

    /**
     * 向前的偏移值（锁定到相机时）
     */
    public forward: number;

    constructor(name: string, scene: Scene, options?: IUINodeOptions) {
        const ops = options || {};
        this.name = name;
        this.key = ops.key || name;
        this._scene = scene;
        this._camera = scene.activeCamera;
        this.container = new TransformNode(name + '-container', this._scene);
        this.root = new TransformNode(name + '-root', this._scene);
        this.container.parent = this.root;

        this._xrRatio = ops.xrRatio ? { x: ops.xrRatio.x, y: ops.xrRatio.y } : { x: 1, y: 1 };
        this.position = this._position.copyFrom(ops.position || new Vector3(0, 0, 0));
        this._size = ops.size || 0.5;
        this.size = 1;
        this.rotation = ops.rotation || Quaternion.Identity();
        this.forward = Math.max(ops.forward || 1, 0.02);
        this.offset = ops.offset || new Vector3(0, 0, 0);
        if (ops.renderingGroupId !== undefined) this.renderingGroupId = ops.renderingGroupId;
        this.lockToCamera = ops.lockToCamera || false;
        this._depthForward = !!ops.depthForward;
        this.billboard = ops.billboard === undefined ? TransformNode.BILLBOARDMODE_ALL : ops.billboard;
        this.anchor = ops.anchor || new Vector3(0, 0, 0);
        this.display = ops.display === undefined ? true : ops.display;

        this._cameraSwitchHandle = (): void => {
            if (!Helper.isCameraGeneral(scene.activeCamera)) return;
            if (this._camera && (this._camera.uniqueId === this._scene.activeCamera?.uniqueId)) return;
            this._camera = this._scene.activeCamera;
            this.onCameraSwitch();
        };
        this._scene.onActiveCameraChanged.add(this._cameraSwitchHandle);
    }

    /**
     * 设置是否显示
     */
    public set display(display: boolean) {
        const s = display ? 1 : -1e-6;
        this.container.scaling.set(s, s, s);
    }

    /**
     * 获取是否显示
     */
    public get display(): boolean {
        return this.container.scaling.x > 0;
    }

    /**
     * 设置锚点
     */
    public set anchor(position: Vector3) {
        this._anchor.copyFrom(position);
        this.updateAnchorPosition();
    }

    /**
     * 获取锚点
     */
    public get anchor(): Vector3 {
        return this._anchor;
    }

    /**
     * 定位到相机
     */
    public toCamera(): void {
        const cam = this._camera;
        if (!cam) return;
        const pos = cam.globalPosition;
        const dir = cam.getDirection(Axis.Z);
        this._tv0.copyFrom(pos).addInPlace(dir.scale(BabylonContainer.isRightHand ? -this.forward : this.forward));
        this.root.position.copyFrom(this._tv0);
    }

    /**
     * 设置是否锁定到相机（场景初始化完成之后，新实例化的ui节点如果需要锁定到相机，需要手动设置一次）
     */
    public set lockToCamera(isLock: boolean) {
        this.attachToCamera(isLock, true);
        this.updateAnchorPosition();
    }

    /**
     * 获取是否锁定到相机
     */
    public get lockToCamera(): boolean {
        return this._lockToCamera;
    }

    /**
     * 设置位置偏移值
     */
    public set offset(offset: Vector3) {
        this.container.position = offset;
    }

    /**
     * 获取位置偏移值
     */
    public get offset(): Vector3 {
        return this.container.position;
    }

    /**
     * 设置渲染顺序值
     */
    public set renderingGroupId(id: number) {
        this._renderGroupId = id;
        const meshes = this.container.getChildMeshes(false);
        for (const m of meshes) {
            m.renderingGroupId = id;
        }
    }

    /**
     * 获取渲染顺序值
     */
    public get renderingGroupId(): number {
        return this._renderGroupId;
    }

    /**
     * 设置朝向模式
     */
    public set billboard(billboard: number) {
        if (this._lockToCamera) return;
        this.root.billboardMode = billboard;
    }

    /**
     * 获取朝向模式
     */
    public get billboard(): number {
        return this.root.billboardMode;
    }

    /**
     * 设置旋转
     */
    public set rotation(rotation: Quaternion) {
        this.container.rotationQuaternion = rotation;
    }

    /**
     * 获取旋转
     */
    public get rotation(): Quaternion {
        return this.container.rotationQuaternion!;
    }

    /**
     * 设置尺寸
     */
    public set size(size: number) {
        const s = this._size * size;
        this.root.scaling.set(s, s, s);
    }

    /**
     * 获取尺寸
     */
    public get size(): number {
        return this.root.scaling.x / this._size;
    }

    /**
     * 设置位置
     */
    public set position(position: Vector3) {
        this.root.position = this._position.copyFrom(position);
    }

    /**
     * 获取位置
     */
    public get position(): Vector3 {
        return this._position;
    }

    /**
     * 窗口尺寸改变时的响应函数
     */
    public resize(): void {
        this.updateAnchorPosition();
    }

    /**
     * 添加ui控件
     * @param controls 控件集合
     */
    public addControl(...controls: Array<AbstractControl | AbstractGroup | AbstractComposite>): void {
        for (const control of controls) {
            if (this.findControl(control) >= 0) continue;
            this._controls.push(control);
            control.container.parent = this.container;
            if (!(control instanceof AbstractComposite)) control.renderingGroupId = this._renderGroupId;
        }
    }

    /**
     * 移除ui控件
     * @param controls 要移除的控件集合或控件id集合
     */
    public removeControl(...controls: Array<AbstractControl | AbstractGroup | AbstractComposite> | number[]): void {
        for (const control of controls) {
            const i = this.findControl(control);
            if (i < 0) continue;
            const ctl = this._controls[i];
            ctl.container.parent = null;
            ctl.container.dispose(false, true);
            ctl.dispose();
            this._controls.splice(i, 1);
        }
    }

    /**
     * 清空ui控件
     */
    public clearControl(): void {
        this.removeControl(...this._controls);
    }

    /**
     * 获取ui控件
     * @param key 键名、id或索引
     * @param isIndex 优先按索引获取
     * @returns ui控件
     */
    public getControl(key: string | number, isIndex?: boolean): AbstractControl | AbstractGroup | AbstractComposite | null {
        const isNm = typeof key === 'number';
        const controls = this._controls;
        if (isNm && isIndex) {
            if (controls[key]) return controls[key];
        }
        for (const control of controls) {
            if (isNm ? control.id === key : control.key === key) {
                return control;
            }
        }
        return null;
    }

    /**
     * 查询控件
     * @param control ui控件或其id 
     * @returns 控件在集合中的索引
     */
    private findControl(control: number | AbstractControl | AbstractGroup | AbstractComposite): number {
        const isID = typeof control === 'number';
        const controls = this._controls;
        for (let i = 0; i < controls.length; i++) {
            if (isID ? control === controls[i].id : control.id === controls[i].id) return i;
        }
        return -1;
    }

    /**
     * 切换活动相机时的响应函数
     */
    private onCameraSwitch(): void {
        if (!this._lockToCamera) return;
        this.attachToCamera(false, false);
        this.updateAnchorPosition();
        this.attachToCamera(this._lockToCamera, false);
    }

    /**
     * 挂载到相机
     * @param isLock 是否锁定到相机
     * @param record 是否记录当前挂载状态
     */
    private attachToCamera(isLock: boolean, record: boolean): void {
        if (record) this._lockToCamera = isLock;
        const cam = this._camera;
        if (!cam) return;
        const root = this.root;
        if (isLock) {
            this._tmpBillboard = this.root.billboardMode;
            this.root.billboardMode = TransformNode.BILLBOARDMODE_NONE;
            root.position.copyFrom(this._scnToWld);
            root.parent = (cam instanceof WebXRCamera) ? cam.rigCameras[0] : cam;
        } else {
            root.parent = null;
            this.position = this._position;
            this.root.billboardMode = this._tmpBillboard;
        }
    }

    /**
     * 更新锚点位置
     */
    private updateAnchorPosition(): void {
        if (!this._lockToCamera) return;
        const camera = this._camera;
        const isXrCam = camera instanceof WebXRCamera;
        if (!camera) return;
        if (!camera.inputs) return;
        const aRat = isXrCam ? this._xrRatio : { x: 1, y: 1 };

        const rotOld = new Quaternion();
        const matNew = new Matrix();
        let fov = camera.fov;
        if (isXrCam) {
            fov = 0.8;
            let ulr = new Vector3();
            rotOld.copyFrom(camera.rotationQuaternion);
            if (camera.rotationQuaternion) {
                camera.rotationQuaternion.toEulerAnglesToRef(ulr);
            } else {
                ulr.copyFrom(camera.rotation);
                camera.rotationQuaternion = Quaternion.Identity();
            }
            ulr.z = 0;
            Quaternion.FromEulerVectorToRef(ulr, camera.rotationQuaternion);
            matNew.copyFrom(camera.getWorldMatrix());
            camera.rotationQuaternion.copyFrom(rotOld);
        } else {
            matNew.copyFrom(camera.getWorldMatrix());
        }

        const root = this.root;
        const stw = this._scnToWld;
        const w = this._scene.getEngine().getRenderWidth();
        const h = this._scene.getEngine().getRenderHeight();
        const x = this.anchor.x * aRat.x + (1 - aRat.x) * 0.5;
        const _y = this.anchor.y * aRat.y + (1 - aRat.y) * 0.5;
        const y = BabylonContainer.isRightHand ? 1 - _y : _y;
        const point = BabylonContainer.isRightHand ? this._tv0.set(x * w, (1 - y) * h, 0) : this._tv0.set(x * w, y * h, 0);

        const asw = camera.viewport.width * w;
        const ash = camera.viewport.height * h;
        const prj = BabylonContainer.isRightHand ? Matrix.PerspectiveFovRH(fov, asw / ash, camera.minZ, camera.maxZ) :
            Matrix.PerspectiveFovLH(fov, asw / ash, camera.minZ, camera.maxZ);
        this._tv1.copyFrom(BabylonContainer.forward);
        this._tv1.applyRotationQuaternionInPlace(new Quaternion().fromRotationMatrix(matNew));

        const tar = this._tv1.addInPlace(camera.position);
        const viw = BabylonContainer.isRightHand ? Matrix.LookAtRH(camera.position, tar, Axis.Y) :
            Matrix.LookAtLH(camera.position, tar, Axis.Y);
        stw.copyFrom(Vector3.Unproject(point, w, h, matNew, viw, prj));
        stw.normalize();

        let len = this.forward;
        if (this._depthForward) {
            const pLen = Vector3.Dot(stw, BabylonContainer.forward);
            len = this.forward / pLen;
        }

        stw.scaleInPlace(len);
        root.position.copyFrom(stw);
    }

}

/**
 * 创建ui节点
 * @param name 名称
 * @param scene 所在场景
 * @param options 参数对象
 * @param children 子级ui控件
 * @returns ui节点
 */
const CreateUINode = (name: string, scene: Scene, options?: IUINodeOptions, children?: Array<AbstractControl | AbstractGroup | AbstractComposite | undefined | null>) => {
    const dp = options ? (options.display === undefined ? true : options.display) : true;
    const ops = options || {};
    ops.display = true;
    const node = new UINode(name, scene, ops);
    const cns: Array<AbstractControl | AbstractGroup | AbstractComposite> = [];
    if (children) {
        for (const c of children) {
            if (c) cns.push(c);
        }
    }
    node.addControl(...cns);
    Utils.sleep(0).then(() => { node.display = dp });
    return node;
};


export { CreateUINode, UINode };
export type { IUINodeOptions };